/*
 * Copyright (c) 2007 The Trustees of Indiana University. All rights reserved.
 * IU CSCI B534 Distributed Systems 2007-02-01 http://www.cs.indiana.edu/classes/b534
 */

package edu.indiana.cs.b534.torrent.struct;

import edu.indiana.cs.b534.torrent.TDictionary;
import edu.indiana.cs.b534.torrent.TString;
import edu.indiana.cs.b534.torrent.TStruct;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.TreeMap;

public class TDictionaryImpl<T extends TStruct> implements TDictionary<T> {

    protected Map<TString, T> dictionary;
    public static final boolean DEBUG = Boolean.parseBoolean(System.getProperty("debug"));

    public static TDictionary deserialize(InputStream inStream) throws IOException {
        // to support mark operation
        byte by;
        // read the list prefix char 'l'
        by = (byte)inStream.read();
        if (DEBUG) System.out.println("D> " + ((char)by));
        if(by != 'd') throw new IOException("Not a valid dictionary encoding. Does not start with char 'd'");

        // create map instance
        Map<TString, TStruct> map = new HashMap<TString, TStruct>();
        // while end of dictionary is not reached, keep looking
        inStream.mark(2);
        by = (byte)inStream.read();
        if (DEBUG) System.out.println("D> " + ((char)by));
        inStream.reset();
        while(by != 'e'){
            // read the string key
            TString key = TStringImpl.deserialize(inStream);
            if (DEBUG) System.out.println("DKEY=" + key);

            // read the next char to decide what the data structure of the value is...
            inStream.mark(2);
            by = (byte)inStream.read();
            if (DEBUG) System.out.println("D> " + ((char)by));
            inStream.reset();

            TStruct value;
            switch(by){
                // parse the child data struct using it's parser
                case 'i' :
                    // starts with 'i'; must be an integer
                    value = TIntegerImpl.deserialize(inStream);
                    break;
                case 'd' :
                    // starts with 'd'; must be a dictionary
                    value = TDictionaryImpl.deserialize(inStream);
                    break;
                case 'l' :
                    // starts with 'l'; must be a list
                    value = TListImpl.deserialize(inStream);
                    break;
                default :
                    // it has to be a string
                    value = TStringImpl.deserialize(inStream);
            }
            if (DEBUG) System.out.println("DVALUE=" + value);
            map.put(key, value);

            inStream.mark(2);
            by = (byte)inStream.read();
            if (DEBUG) System.out.println("D> " + ((char)by));
            inStream.reset();
        }
        by = (byte)inStream.read(); // read the 'e' character that was reset

        // create and return an instance
        TDictionary<TStruct> instance = newInstance();
        instance.setDictionary(map);
        return instance;
    }

    public static TDictionary<TStruct> newInstance() {
        return new TDictionaryImpl<TStruct>();
    }

    protected TDictionaryImpl() {
        dictionary = new HashMap<TString, T>();
    }

    public Map<TString, T> getDictionary() {

        return dictionary;
    }

    public void setDictionary(Map<TString, T> dictionary_) {
        dictionary = dictionary_;
    }

    public OutputStream serialize(OutputStream outStream) throws IOException {

        TreeMap<TString, T> sortedMap = new TreeMap<TString, T>(new TStringComparator());
        sortedMap.putAll(dictionary);

        outStream.write('d'); // prefix
        for(Map.Entry<TString, T> entry : sortedMap.entrySet()){
            entry.getKey().serialize(outStream);
            entry.getValue().serialize(outStream);
        }
        outStream.write('e'); // suffix

        return outStream;
    }

//    public void putString(String key,String value){
//    	T t = (T)TStringImpl.newInstance(value);
//    	((Map)dictionary).put(TStringImpl.newInstance(key), t);
//    }
    
    
    public String toString(){
        StringBuffer buffer = new StringBuffer("\nTDictionary[");
        buffer.append(dictionary.size());
        buffer.append("]:\n");
        for(Map.Entry<TString, ? extends TStruct> entry : dictionary.entrySet()){
            buffer.append('[');
            buffer.append(entry.getKey());
            buffer.append("]=[");
            buffer.append(entry.getValue());
            buffer.append("]\n");
        }
        buffer.append('\n');
        return buffer.toString();
    }

}
